/**
 * Copyright 2002-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fr.generali.accueilclient.utils.spel.mapping.support;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.core.convert.ConversionService;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;

import fr.generali.accueilclient.utils.spel.mapping.MappingException;
import fr.generali.accueilclient.utils.spel.mapping.MappingFailure;

/**
 * @author Holmes Kelly
 */
final class SpelMappingContext {

    /**
     * 
     */
    private final MappableType sourceType;

    /**
     * 
     */
    private final EvaluationContext sourceEvaluationContext;

    /**
     * 
     */
    private final EvaluationContext targetEvaluationContext;

    /**
     * failures
     */
    private final List<MappingFailure> failures = new LinkedList<MappingFailure>();

    /**
     * @param source source
     * @param sourceType source type
     * @param target target
     * @param targetType target type
     * @param conversionService conversion service
     */
    public SpelMappingContext(Object source, MappableType sourceType, Object target, MappableType targetType,
                    ConversionService conversionService) {
        this.sourceType = sourceType;
        this.sourceEvaluationContext = sourceType.getEvaluationContext(source, conversionService);
        this.targetEvaluationContext = targetType.getEvaluationContext(target, conversionService);
    }

    /**
     * @return source
     */
    public Object getSource() {
        return this.sourceEvaluationContext.getRootObject().getValue();
    }

    /**
     * @return target
     */
    public Object getTarget() {
        return this.targetEvaluationContext.getRootObject().getValue();
    }

    /**
     * @param condition condition
     * @return condition holds
     */
    public boolean conditionHolds(Expression condition) {
        if (condition == null) {
            return true;
        }
        return Boolean.TRUE.equals(condition.getValue(this.sourceEvaluationContext));
    }

    /**
     * @param sourceField source field
     * @return source field value
     */
    public Object getSourceFieldValue(Expression sourceField) {
        return sourceField.getValue(this.sourceEvaluationContext);
    }

    /**
     * @return source field names
     */
    public Set<String> getSourceFieldNames() {
        return this.sourceType.getFieldNames(getSource());
    }

    /**
     * @param sourceFieldName source field name
     * @return map source nested field
     */
    public Map<String, Object> getSourceNestedFields(String sourceFieldName) {
        return this.sourceType.getNestedFields(sourceFieldName, getSource());
    }

    /**
     * @param targetField target field
     * @param value value
     */
    public void setTargetFieldValue(Expression targetField, Object value) {
        targetField.setValue(this.targetEvaluationContext, value);
    }

    /**
     * @param cause cause
     */
    public void addMappingFailure(Throwable cause) {
        this.failures.add(new MappingFailure(cause));
    }

    /**
     * handle failures
     */
    public void handleFailures() {
        if (!this.failures.isEmpty()) {
            throw new MappingException(this.failures);
        }
    }

    /**
     * @param targetField target field
     * @return true if target field is writable
     */
    public boolean isTargetFieldWriteable(Expression targetField) {
        return targetField.isWritable(this.targetEvaluationContext);
    }

}
